From d74cacb7752c00473af3416e8c195ff84c560b84 Mon Sep 17 00:00:00 2001 From: tsteven4 Date: Sun, 19 Aug 2018 16:03:50 -0600 Subject: [PATCH] use QVariant to store exif tag data. enhance exif debug capability. --- exif.cc | 478 +++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 301 insertions(+), 177 deletions(-) diff --git a/exif.cc b/exif.cc index c36e99b61..de7846d81 100644 --- a/exif.cc +++ b/exif.cc @@ -40,26 +40,30 @@ #include "jeeps/gpsmath.h" // for GPS_Math_WGS84_To_Known_Datum_M #include "src/core/datetime.h" // for DateTime #include // for QByteArray +#include // for QDate #include // for QDateTime #include // for QFile #include // for QList<>::iterator, QList #include // for QString #include // for QTextCodec +#include // for QTime +#include // for QVariant +#include // for QVector +#include // for ISODate, UTC #include // for qPrintable -#include // for sort +#include // for sort, min #include // for assert #include // for DBL_EPSILON -#include // for fabs, floor -#include // for snprintf, SEEK_SET, sscanf -#include // for int32_t, int16_t, uint16_t, uint32_t, uint8_t -#include // for labs, atoi, abs -#include // for strrchr, memcmp, memcpy, memset, strchr, strlen, strncat +#include // for fabs, modf, copysign, round, fmax +#include // for int32_t, int16_t, uint16_t, uint32_t, uint8_t, INT32_MAX +#include // for printf, snprintf, SEEK_SET, SEEK_CUR +#include // for labs, atoi +#include // for strrchr, memcmp, strchr, strlen #include // for gmtime, localtime, time_t, tm +#include // for isprint, isspace #define MYNAME "exif" -// #define EXIF_DBG - #define UNKNOWN_TIMESTAMP 999999999 #define IFD0 0 @@ -122,24 +126,36 @@ struct ExifTag { uint16_t type{0}; // field type. uint32_t count{0}; // number of values. Note that Count is not the total number of bytes. uint32_t offset{0}; // byte offset relative to beginning of TIFF file to value (only for values longer than 4 bytes). - uint32_t original{0}; + QVector data; // value uint32_t size{0}; // derived size in bytes of value. -#ifdef EXIF_DBG - uint32_t tag_offset {0}; // byte offset relative to beginning of TIFF file of this tag. -#endif - QByteArray data; + + uint32_t tag_offset {0}; // byte offset relative to beginning of TIFF file of this tag, for debug only. + uint8_t raw[4] {0,0,0,0}; // raw value/offset data, for debug only. bool operator==(const ExifTag& other) const { return id == other.id; } + // grow data vector, initializing any new elements to type T with value 0. + template + void grow(const int size) + { + int old_size = data.size(); + if (size > old_size) { + data.resize(size); + for (int idx = old_size; idx < size; ++idx) { + data[idx] = 0; + } + } + } + // Return data value interpreted as EXIF_TYPE_LONG. // This is most useful when the type is EXIF_TYPE_LONG and the count is one, // which occurs for multiple specific tags where we need the value. inline uint32_t toLong() const { - return *reinterpret_cast(data.constData()); + return data.at(0).value(); } }; @@ -188,7 +204,7 @@ static arglist_t exif_args[] = { ARG_TERMINATOR }; -#ifdef EXIF_DBG +// for debug only static void print_buff(const char* buf, int sz, const char* cmt) { @@ -208,7 +224,6 @@ print_buff(const char* buf, int sz, const char* cmt) printf("%c", c); } } -#endif static uint16_t exif_type_size(const uint16_t type) @@ -218,8 +233,8 @@ exif_type_size(const uint16_t type) switch (type) { case EXIF_TYPE_BYTE: case EXIF_TYPE_ASCII: - case EXIF_TYPE_UNK: case EXIF_TYPE_SBYTE: + case EXIF_TYPE_UNK: size = 1; break; @@ -268,7 +283,8 @@ static char* exif_read_str(ExifTag* tag) { // Panasonic DMC-TZ10 stores datum with trailing spaces. - char* buf = xstrndup(tag->data.constData(), tag->size); + // Kodak stores zero count ASCII tags. + char* buf = (tag->count == 0) ? xstrdup("") : xstrndup(tag->data.at(0).toByteArray().constData(), tag->size); rtrim(buf); return buf; } @@ -276,15 +292,15 @@ exif_read_str(ExifTag* tag) static double exif_read_double(const ExifTag* tag, const int index) { - int32_t* data = (int32_t*)tag->data.constData(); - - unsigned int num = data[index * 2]; - unsigned int den = data[(index * 2) + 1]; - if (den == 0) { - den = 1; + if (tag->type == EXIF_TYPE_RAT) { + uint32_t num = tag->data.at(index * 2).value(); + uint32_t den = tag->data.at((index * 2) + 1).value(); + return (double)num / (double)den; + } else { // EXIF_TYPE_SRAT + int32_t num = tag->data.at(index * 2).value(); + int32_t den = tag->data.at((index * 2) + 1).value(); + return (double)num / (double)den; } - - return (double)num / (double)den; } static double @@ -307,30 +323,20 @@ exif_read_coord(const ExifTag* tag) return res; } -static time_t +static QTime exif_read_timestamp(const ExifTag* tag) { double hour = exif_read_double(tag, 0); double min = exif_read_double(tag, 1); double sec = exif_read_double(tag, 2); - return ((int)hour * SECONDS_PER_HOUR) + ((int)min * 60) + (int)sec; + return QTime(int(hour), int(min), int(sec)); } -static time_t +static QDate exif_read_datestamp(const ExifTag* tag) { - struct tm tm; - - memset(&tm, 0, sizeof(tm)); - char* str = xstrndup(tag->data.constData(), tag->size); - sscanf(str, "%d:%d:%d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday); - xfree(str); - - tm.tm_year -= 1900; - tm.tm_mon -= 1; - - return mkgmtime(&tm); + return QDate::fromString(tag->data.at(0).toByteArray().constData(), "yyyy:MM:dd"); } static void @@ -372,14 +378,14 @@ exif_load_apps() app->marker = gbfgetuint16(fin); app->len = gbfgetuint16(fin); -#ifdef EXIF_DBG - printf(MYNAME ": api = %02X, len = %u (0x%04x), offs = 0x%08X\n", app->marker & 0xFF, app->len, app->len, gbftell(fin)); -#endif - if (exif_app || (app->marker == 0xFFDA)) /* compressed data */ { + if (global_opts.debug_level >= 3) { + printf(MYNAME ": api = %02X, len = %u (0x%04x), offs = 0x%08X\n", app->marker & 0xFF, app->len, app->len, gbftell(fin)); + } + if (exif_app || (app->marker == 0xFFDA)) { /* compressed data */ gbfcopyfrom(app->fcache, fin, 0x7FFFFFFF); -#ifdef EXIF_DBG - printf(MYNAME ": compressed data size = %d\n", gbftell(app->fcache)); -#endif + if (global_opts.debug_level >= 3) { + printf(MYNAME ": compressed data size = %d\n", gbftell(app->fcache)); + } } else { gbfcopyfrom(app->fcache, fin, app->len - 2); if (app->marker == 0xFFE1) { @@ -391,6 +397,32 @@ exif_load_apps() return exif_app; } +static void +exif_validate_tag_structure(const ExifTag* tag) +{ + // The count times the element size should match the saved size. + assert((tag->count * exif_type_size(tag->type)) == tag->size); + + // for BYTE_TYPE we store a QByteArray as the only component of the QVector, + // and the count should match the size of the byte array. + assert((!BYTE_TYPE(tag->type)) || + ((tag->data.size() == 0) && (tag->count == 0)) || + ((tag->data.size() == 1) && (static_cast(tag->data.at(0).toByteArray().size()) == tag->count))); + + // EXIF_TYPE_RAT and EXIF_TYPE_SRAT are stored as two values per item. + assert(((tag->type != EXIF_TYPE_RAT) && (tag->type != EXIF_TYPE_SRAT)) || + (static_cast(tag->data.size()) == (2 * tag->count))); + + // types other that BYTE_TYPE, RAT, SRAT are stored as one value per item. + assert(BYTE_TYPE(tag->type) || (tag->type == EXIF_TYPE_RAT) || (tag->type == EXIF_TYPE_SRAT) || + (static_cast(tag->data.size()) == tag->count)); + + // For EXIF_TYPE_ASCII the last byte of the value must be NUL (binary 0). + assert((tag->type != EXIF_TYPE_ASCII) || + ((tag->data.size() == 0) && (tag->count == 0)) || + ((tag->data.size() == 1) && (tag->data.at(0).toByteArray().endsWith('\0')))); +} + static ExifIfd* exif_read_ifd(ExifApp* app, const uint16_t ifd_nr, const gbsize_t offs, uint32_t* exif_ifd_ofs, uint32_t* gps_ifd_ofs, uint32_t* inter_ifd_ofs) @@ -404,8 +436,7 @@ exif_read_ifd(ExifApp* app, const uint16_t ifd_nr, const gbsize_t offs, gbfseek(fin, offs, SEEK_SET); ifd->count = gbfgetuint16(fin); -#ifdef EXIF_DBG - { + if (global_opts.debug_level >= 3) { const char* name; switch (ifd_nr) { case IFD0: @@ -430,7 +461,6 @@ exif_read_ifd(ExifApp* app, const uint16_t ifd_nr, const gbsize_t offs, printf(MYNAME "-offs 0x%08X: Number of items in IFD%d \"%s\" = %d (0x%04x)\n", offs, ifd_nr, name, ifd->count, ifd->count); } -#endif if (ifd->count == 0) { return ifd; } @@ -438,36 +468,51 @@ exif_read_ifd(ExifApp* app, const uint16_t ifd_nr, const gbsize_t offs, for (uint16_t i = 0; i < ifd->count; i++) { ifd->tags.append(ExifTag()); ExifTag* tag = &ifd->tags.last(); -#ifdef EXIF_DBG - tag->tag_offset = gbftell(fin); -#endif + if (global_opts.debug_level >= 3) { + tag->tag_offset = gbftell(fin); + } tag->id = gbfgetuint16(fin); tag->type = gbfgetuint16(fin); tag->count = gbfgetuint32(fin); tag->size = exif_type_size(tag->type) * tag->count; - tag->data = QByteArray(4, 0); if (tag->size <= 4) { // data is in value offset field if (BYTE_TYPE(tag->type)) { - gbfread(tag->data.data(), 4, 1, fin); + assert(tag->count <= 4); + if (tag->count > 0) { + QByteArray qba(tag->count, 0); + gbfread(qba.data(), tag->count, 1, fin); + tag->data.append(qba); + } } else if (WORD_TYPE(tag->type)) { - uint16_t* ptr = reinterpret_cast(tag->data.data()); - *ptr = gbfgetuint16(fin); - *(ptr+1) = gbfgetuint16(fin); + assert(tag->count <= 2); + for (unsigned idx=0; idx < tag->count; ++idx) { + tag->data.append(gbfgetuint16(fin)); + } } else if (LONG_TYPE(tag->type)) { - uint32_t* ptr = reinterpret_cast(tag->data.data()); - *ptr = gbfgetuint32(fin); + assert(tag->count <= 1); + if (tag->count == 1) { + tag->data.append(gbfgetuint32(fin)); + } } else if (tag->type == EXIF_TYPE_FLOAT) { - float* ptr = reinterpret_cast(tag->data.data()); - *ptr = gbfgetflt(fin); + assert(tag->count <= 1); + if (tag->count == 1) { + tag->data.append(gbfgetflt(fin)); + } } else { fatal(MYNAME "Unknown type %d has size <= 4! Please report.\n", tag->type); } - tag->original = tag->toLong(); + int skip_bytes = 4 - tag->size; + if (skip_bytes > 0) { + gbfseek(fin, skip_bytes, SEEK_CUR); + } + if (global_opts.debug_level >= 3) { + gbfseek(fin, -4, SEEK_CUR); + gbfread(tag->raw, 4, 1, fin); + } } else { // offset is in value offset field tag->offset = gbfgetuint32(fin); - tag->original = tag->offset; } if (ifd_nr == IFD0) { @@ -483,70 +528,108 @@ exif_read_ifd(ExifApp* app, const uint16_t ifd_nr, const gbsize_t offs, } } -#ifdef EXIF_DBG - gbsize_t next_ifd_offs = gbftell(fin); -#endif + gbsize_t next_ifd_offs; + if (global_opts.debug_level >= 3) { + next_ifd_offs = gbftell(fin); + } ifd->next_ifd = gbfgetuint32(fin); for (auto& tag_instance : ifd->tags) { ExifTag* tag = &tag_instance; if ((tag->size > 4) && (tag->offset)) { - tag->data = QByteArray(tag->size, 0); - - char* ptr = tag->data.data(); gbfseek(fin, tag->offset, SEEK_SET); if (BYTE_TYPE(tag->type)) { - gbfread(ptr, tag->count, 1, fin); + QByteArray qba(tag->count, 0); + gbfread(qba.data(), tag->count, 1, fin); + tag->data.append(qba); } else for (uint16_t i = 0; i < tag->count; i++) { switch (tag->type) { case EXIF_TYPE_SHORT: case EXIF_TYPE_SSHORT: - *(int16_t*)ptr = gbfgetuint16(fin); + tag->data.append(gbfgetuint16(fin)); break; case EXIF_TYPE_IFD: case EXIF_TYPE_LONG: case EXIF_TYPE_SLONG: - *(int32_t*)ptr = gbfgetuint32(fin); + tag->data.append(gbfgetuint32(fin)); break; case EXIF_TYPE_RAT: case EXIF_TYPE_SRAT: - *(int32_t*)ptr = gbfgetuint32(fin); - *(int32_t*)(ptr+4) = gbfgetuint32(fin); + tag->data.append(gbfgetuint32(fin)); + tag->data.append(gbfgetuint32(fin)); break; case EXIF_TYPE_FLOAT: - *(float*)ptr = gbfgetflt(fin); + tag->data.append(gbfgetflt(fin)); break; case EXIF_TYPE_DOUBLE: - *(double*)ptr = gbfgetdbl(fin); - break; - default: - gbfread(ptr, exif_type_size(tag->type), 1, fin); + tag->data.append(gbfgetdbl(fin)); break; + default: { + // We know the size for this tag type, but not the layout. + // Save it is a byte array we can echo on write. + QByteArray qba(tag->count, 0); + gbfread(qba.data(), exif_type_size(tag->type), 1, fin); + tag->data.append(qba); + } + break; } - ptr += (tag->size / tag->count); } } -#ifdef EXIF_DBG - printf(MYNAME "-offs 0x%08X: ifd=%d id=0x%04X t=0x%04X c=%4u s=%4u", - tag->tag_offset, ifd->nr, tag->id, tag->type, tag->count, tag->size); - if (tag->size > 4) { - printf(" o=0x%08X", tag->offset); - } else { - printf(" v=0x%08X", tag->toLong()); - } - if (tag->type == EXIF_TYPE_ASCII) { - char* str = exif_read_str(tag); - printf(" \"%s\"", str); - xfree(str); + if (global_opts.debug_level >= 3) { + printf(MYNAME "-offs 0x%08X: ifd=%d id=0x%04X t=0x%04X c=%4u s=%4u", + tag->tag_offset, ifd->nr, tag->id, tag->type, tag->count, tag->size); + if (tag->size > 4) { + printf(" o=0x%08X", tag->offset); + } else { + printf(" v=0x%02X%02X%02X%02X", tag->raw[0], tag->raw[1], tag->raw[2], tag->raw[3]); + } + if (tag->type == EXIF_TYPE_ASCII) { + char* str = exif_read_str(tag); + printf(" \"%s\"", str); + xfree(str); + } else { + for (unsigned idx = 0; idx < std::min(tag->count, 4u); ++idx) { + if (tag->type == EXIF_TYPE_BYTE) { + printf(" %u", tag->data.at(0).toByteArray().at(idx)); + } else if (tag->type == EXIF_TYPE_SBYTE) { + printf(" %d", tag->data.at(0).toByteArray().at(idx)); + } else if (tag->type == EXIF_TYPE_UNK) { + printf(" 0x%02X", tag->data.at(0).toByteArray().at(idx)); + } else if (tag->type == EXIF_TYPE_RAT) { + printf(" %+#g(%u/%u)", exif_read_double(tag, idx), tag->data.at(idx * 2).value(), tag->data.at((idx * 2) + 1).value()); + } else if (tag->type == EXIF_TYPE_SRAT) { + printf(" %+#g(%d/%d)", exif_read_double(tag, idx), tag->data.at(idx * 2).value(), tag->data.at((idx * 2) + 1).value()); + } else if (tag->type == EXIF_TYPE_SHORT) { + printf(" %u", tag->data.at(idx).value()); + } else if (tag->type == EXIF_TYPE_SSHORT) { + printf(" %d", tag->data.at(idx).value()); + } else if (tag->type == EXIF_TYPE_LONG) { + printf(" %u", tag->data.at(idx).value()); + } else if (tag->type == EXIF_TYPE_SLONG) { + printf(" %d", tag->data.at(idx).value()); + } else if (tag->type == EXIF_TYPE_FLOAT) { + printf(" %+#g", tag->data.at(idx).value()); + } else if (tag->type == EXIF_TYPE_DOUBLE) { + printf(" %+#g", tag->data.at(idx).value()); + } else { + printf(" 0x%0*X", 2 * exif_type_size(tag->type), tag->data.at(idx).value()); + } + } + if (tag->count > 4) { + printf(" ..."); + } + } + printf("\n"); } - printf("\n"); +#ifndef NDEBUG + exif_validate_tag_structure(tag); #endif } -#ifdef EXIF_DBG - printf(MYNAME "-offs 0x%08X: Next IFD=0x%08X\n", next_ifd_offs, ifd->next_ifd); -#endif + if (global_opts.debug_level >= 3) { + printf(MYNAME "-offs 0x%08X: Next IFD=0x%08X\n", next_ifd_offs, ifd->next_ifd); + } return ifd; } @@ -559,11 +642,11 @@ exif_read_app(ExifApp* app) ExifIfd* ifd; gbfile* fin = app->fexif; -#ifdef EXIF_DBG - printf(MYNAME ": read_app...\n"); - print_buff((const char*)fin->handle.mem, 8, MYNAME "-offs 0x00000000: Image File Header"); - printf("\n"); -#endif + if (global_opts.debug_level >= 3) { + printf(MYNAME ": read_app...\n"); + print_buff((const char*)fin->handle.mem, 8, MYNAME "-offs 0x00000000: Image File Header"); + printf("\n"); + } exif_ifd_ofs = gps_ifd_ofs = inter_ifd_ofs = 0; gbfseek(fin, 4, SEEK_SET); @@ -601,9 +684,9 @@ exif_examine_app(ExifApp* app) is_fatal(gbfgetint16(ftmp) != 0, MYNAME ": Error in EXIF header."); uint16_t endianess = gbfgetint16(ftmp); -#ifdef EXIF_DBG - printf(MYNAME ": endianess = 0x%04X\n", endianess); -#endif + if (global_opts.debug_level >= 3) { + printf(MYNAME ": endianess = 0x%04X\n", endianess); + } if (endianess == 0x4949) { ftmp->big_endian = 0; } else if (endianess == 0x4D4D) { @@ -681,8 +764,9 @@ exif_waypt_from_exif_app(ExifApp* app) char mode = '\0'; double gpsdop = unknown_alt; double alt = unknown_alt; - time_t timestamp = UNKNOWN_TIMESTAMP; - time_t datestamp = UNKNOWN_TIMESTAMP; + QTime timestamp; + QDate datestamp; + QDateTime gps_datetime; ExifIfd* ifd = exif_find_ifd(app, GPS_IFD); if (ifd == nullptr) { @@ -701,19 +785,19 @@ exif_waypt_from_exif_app(ExifApp* app) case GPS_IFD_TAG_VERSION: break; case GPS_IFD_TAG_LATREF: - lat_ref = tag->data.at(0); + lat_ref = tag->data.at(0).toByteArray().at(0); break; case GPS_IFD_TAG_LAT: wpt->latitude = exif_read_coord(tag); break; case GPS_IFD_TAG_LONREF: - lon_ref = tag->data.at(0); + lon_ref = tag->data.at(0).toByteArray().at(0); break; case GPS_IFD_TAG_LON: wpt->longitude = exif_read_coord(tag); break; case GPS_IFD_TAG_ALTREF: - alt_ref = tag->data.at(0); + alt_ref = tag->data.at(0).toByteArray().at(0); break; case GPS_IFD_TAG_ALT: alt = exif_read_double(tag, 0); @@ -722,16 +806,16 @@ exif_waypt_from_exif_app(ExifApp* app) timestamp = exif_read_timestamp(tag); break; case GPS_IFD_TAG_SAT: - wpt->sat = atoi(tag->data.constData()); + wpt->sat = tag->data.at(0).toByteArray().toInt(); break; case GPS_IFD_TAG_MODE: - mode = tag->data.at(0); + mode = tag->data.at(0).toByteArray().at(0); break; case GPS_IFD_TAG_DOP: gpsdop = exif_read_double(tag, 0); break; case GPS_IFD_TAG_SPEEDREF: - speed_ref = tag->data.at(0); + speed_ref = tag->data.at(0).toByteArray().at(0); break; case GPS_IFD_TAG_SPEED: WAYPT_SET(wpt, speed, exif_read_double(tag, 0)); @@ -758,7 +842,7 @@ exif_waypt_from_exif_app(ExifApp* app) if (lon_ref == 'W') { wpt->longitude *= -1; } else if (lon_ref != 'E') { - warning(MYNAME ": GPSLongitudeRef not set! Using E(east).\n"); + warning(MYNAME ": GPSLongitudeRef not set! Using E(ast).\n"); } #ifdef EXIF_DBG @@ -834,20 +918,12 @@ exif_waypt_from_exif_app(ExifApp* app) } } - if (timestamp != UNKNOWN_TIMESTAMP) { - if (datestamp != UNKNOWN_TIMESTAMP) { - timestamp += datestamp; + gps_datetime = QDateTime(datestamp, timestamp, Qt::UTC); + if (gps_datetime.isValid()) { + if (global_opts.debug_level >= 3) { + printf(MYNAME "-GPSTimeStamp = %s\n", qPrintable(gps_datetime.toString(Qt::ISODate))); } - } else { - timestamp = datestamp; - } - - if (timestamp != UNKNOWN_TIMESTAMP) { -#ifdef EXIF_DBG - QString str = QDateTime::fromTime_t(timestamp).toUTC().toString(Qt::ISODate); - printf(MYNAME "-GPSTimeStamp = %s\n", qPrintable(str)); -#endif - wpt->SetCreationTime(timestamp); + wpt->SetCreationTime(gps_datetime); } else { wpt->SetCreationTime(exif_get_exif_time(app)); } @@ -855,16 +931,16 @@ exif_waypt_from_exif_app(ExifApp* app) tag = exif_find_tag(app, EXIF_IFD, EXIF_IFD_TAG_USER_CMT); /* UserComment */ if (tag && (tag->size > 8)) { // TODO: User comments with JIS and Undefined Code Designations are ignored. - if (memcmp(tag->data.constData(), "ASCII\0\0\0", 8) == 0) { - wpt->notes = QString::fromLatin1(tag->data.constData() + 8, tag->size - 8); - } else if (memcmp(tag->data.constData(), "UNICODE\0", 8) == 0) { + if (memcmp(tag->data.at(0).toByteArray().constData(), "ASCII\0\0\0", 8) == 0) { + wpt->notes = QString::fromLatin1(tag->data.at(0).toByteArray().constData() + 8, tag->size - 8); + } else if (memcmp(tag->data.at(0).toByteArray().constData(), "UNICODE\0", 8) == 0) { QTextCodec* utf16_codec; if (app->fcache->big_endian) { utf16_codec = QTextCodec::codecForName("UTF-16BE"); } else { utf16_codec = QTextCodec::codecForName("UTF-16LE"); } - wpt->notes = utf16_codec->toUnicode(tag->data.constData() + 8, tag->size - 8); + wpt->notes = utf16_codec->toUnicode(tag->data.at(0).toByteArray().constData() + 8, tag->size - 8); } } @@ -990,38 +1066,69 @@ exif_put_value(const int ifd_nr, const uint16_t tag_id, const uint16_t type, con tag->type = type; tag->count = index + count; tag->size = size; - tag->data = QByteArray((size < 4) ? 4 : size, 0); ifd->count++; - } else if (size == 0) { /* remove this element */ + } else if (size == 0) { /* remove this tag */ ifd->count--; ifd->tags.removeOne(*tag); return nullptr; - } else if (size > tag->data.size()) { /* modify existing tag */ - tag->data.append(size - tag->data.size(), 0); - tag->size = size; + } else { /* modify existing tag */ tag->count = index + count; + tag->size = size; } - switch (type) { - case EXIF_TYPE_RAT: - case EXIF_TYPE_SRAT: { - double val = *(double*)data; - int32_t* dest = reinterpret_cast(tag->data.data()); - - if ((val < 0.0) && (type == EXIF_TYPE_RAT)) { - fatal(MYNAME ": A negative value cannot be stored as type RATIONAL."); + if (BYTE_TYPE(type)) { + assert(item_size == 1); + if (tag->data.size() == 0) { + tag->data.append(QByteArray()); + } + QByteArray qba = tag->data.at(0).toByteArray(); + int size_increase = (index + count) - qba.size(); + if (size_increase > 0) { + qba.append(size_increase, 0); } + qba.replace(index, count, (char*) data, count); + tag->data[0] = qba; + } else { + // we haven't coded for insertion of multiple values (except for BYTE_TYPE above). + assert(count == 1); + switch (type) { + case EXIF_TYPE_SHORT: + case EXIF_TYPE_SSHORT: + tag->grow(index + count); + tag->data[index] = *(uint16_t*)data; + break; + case EXIF_TYPE_LONG: + case EXIF_TYPE_SLONG: + case EXIF_TYPE_IFD: + tag->grow(index + count); + tag->data[index] = *(uint32_t*)data; + break; + case EXIF_TYPE_RAT: + case EXIF_TYPE_SRAT: { + double val = *(double*)data; - Rational rat = exif_dec2frac(val, 1e-11); - dest[index * 2] = rat.num; - dest[(index * 2) + 1] = rat.den; - } - break; - default: { - char* dest = tag->data.data(); - memcpy(&dest[index * item_size], data, count * item_size); - } + if ((val < 0.0) && (type == EXIF_TYPE_RAT)) { + fatal(MYNAME ": A negative value cannot be stored as type RATIONAL."); + } + + Rational rat = exif_dec2frac(val, 1e-11); + tag->grow((index + count) * 2); + tag->data[index * 2] = rat.num; + tag->data[(index * 2) + 1] = rat.den; + } + break; + case EXIF_TYPE_FLOAT: + tag->grow(index + count); + tag->data[index] = *(float*)data; + break; + case EXIF_TYPE_DOUBLE: + tag->grow(index + count); + tag->data[index] = *(double*)data; + break; + default: + fatal(MYNAME ": Unknown data type %u!\n", type); + } } return tag; } @@ -1030,7 +1137,7 @@ exif_put_value(const int ifd_nr, const uint16_t tag_id, const uint16_t type, con static void exif_put_double(const int ifd_nr, const int tag_id, const int index, const double val) { - // TODO: It seems wrong to toss the sign. + // TODO: It seems wrong to throw away the sign. double d = fabs(val); exif_put_value(ifd_nr, tag_id, EXIF_TYPE_RAT, 1, index, &d); } @@ -1114,19 +1221,32 @@ exif_write_value(ExifTag* tag, gbfile* fout) if (tag->size > 4) { gbfputuint32(tag->offset, fout); /* offset to data */ } else { - char* data = tag->data.data(); - if BYTE_TYPE(tag->type) { - gbfwrite(data, 4, 1, fout); + assert(tag->count <= 4); + if (tag->count > 0) { + gbfwrite(tag->data.at(0).toByteArray().constData(), tag->count, 1, fout); + } } else if WORD_TYPE(tag->type) { - gbfputuint16(*(uint16_t*)data, fout); - gbfputuint16(*(uint16_t*)(data+2), fout); + assert(tag->count <= 2); + for (unsigned idx = 0; idx < tag->count; ++idx) { + gbfputuint16(tag->data.at(idx).value(), fout); + } } else if LONG_TYPE(tag->type) { - gbfputuint32(*(uint32_t*)data, fout); + assert(tag->count <= 1); + if (tag->count > 0) { + gbfputuint32(tag->data.at(0).value(), fout); + } } else if (tag->type == EXIF_TYPE_FLOAT) { - gbfputflt(*(float*)data, fout); + assert(tag->count <= 1); + if (tag->count > 0) { + gbfputflt(tag->data.at(0).value(), fout); + } } else { - fatal(MYNAME ": Unknown data type %d!\n", tag->type); + fatal(MYNAME ": Unknown data type %d or wrong tag size %d!\n", tag->type, tag->size); + } + int fill_bytes = 4 - tag->size; + for (int idx = 0; idx < fill_bytes; ++idx) { + gbfputc(0, fout); } } } @@ -1140,6 +1260,10 @@ exif_write_ifd(ExifIfd* ifd, const char next, gbfile* fout) for (auto& tag_instance : ifd->tags) { ExifTag* tag = &tag_instance; +#ifndef NDEBUG + exif_validate_tag_structure(tag); +#endif + gbfputuint16(tag->id, fout); gbfputuint16(tag->type, fout); gbfputuint32(tag->count, fout); @@ -1165,37 +1289,36 @@ exif_write_ifd(ExifIfd* ifd, const char next, gbfile* fout) ExifTag* tag = &tag_instance; if (tag->size > 4) { - char* ptr = tag->data.data(); - if BYTE_TYPE(tag->type) { - gbfwrite(tag->data.data(), tag->size, 1, fout); + gbfwrite(tag->data.at(0).toByteArray().constData(), tag->size, 1, fout); } else for (uint16_t i = 0; i < tag->count; i++) { switch (tag->type) { case EXIF_TYPE_SHORT: case EXIF_TYPE_SSHORT: - gbfputuint16(*(int16_t*)ptr, fout); + gbfputuint16(tag->data.at(i).value(), fout); break; case EXIF_TYPE_LONG: case EXIF_TYPE_SLONG: case EXIF_TYPE_IFD: - gbfputuint32(*(int32_t*)ptr, fout); + gbfputuint32(tag->data.at(i).value(), fout); break; case EXIF_TYPE_RAT: case EXIF_TYPE_SRAT: - gbfputuint32(*(int32_t*)ptr, fout); - gbfputuint32(*(int32_t*)(ptr+4), fout); + gbfputuint32(tag->data.at(2 * i).value(), fout); + gbfputuint32(tag->data.at((2 * i) + 1).value(), fout); break; case EXIF_TYPE_FLOAT: - gbfputflt(*(float*)ptr, fout); + gbfputflt(tag->data.at(i).value(), fout); break; case EXIF_TYPE_DOUBLE: - gbfputdbl(*(double*)ptr, fout); + gbfputdbl(tag->data.at(i).value(), fout); break; default: - gbfwrite(ptr, exif_type_size(tag->type), 1, fin); + // We know the size for this tag type, but not the layout. + // Echo saved data from read. + gbfwrite(tag->data.at(i).toByteArray().constData(), tag->size, 1, fout); break; } - ptr += (tag->size / tag->count); } if (gbftell(fout) & 1u) { gbfputc(0, fout); @@ -1240,7 +1363,9 @@ exif_write_apps() len += 4; /* DWORD(0) after last ifd */ - if ((exif_find_tag(app, IFD1, IFD1_TAG_JPEG_OFFS))) { + uint32_t original_jpeg_offset; + if ((tag = exif_find_tag(app, IFD1, IFD1_TAG_JPEG_OFFS))) { + original_jpeg_offset = tag->toLong(); exif_put_long(IFD1, IFD1_TAG_JPEG_OFFS, 0, len); } @@ -1267,10 +1392,9 @@ exif_write_apps() gbfputuint32(0, ftmp); /* DWORD(0) after last ifd */ - if ((tag = exif_find_tag(app, IFD1, IFD1_TAG_JPEG_OFFS))) { - gbsize_t offs = tag->original; + if (exif_find_tag(app, IFD1, IFD1_TAG_JPEG_OFFS)) { if ((tag = exif_find_tag(app, IFD1, IFD1_TAG_JPEG_SIZE))) { - gbfseek(app->fexif, offs, SEEK_SET); + gbfseek(app->fexif, original_jpeg_offset, SEEK_SET); gbfcopyfrom(ftmp, app->fexif, tag->toLong()); } } -- 2.30.2